#include "coroutine.h"
#include <iostream>
#include <mutex>
#include <thread>

namespace coroutine {
std::unique_ptr<Schedule> Schedule::schedule = nullptr;

Schedule::Schedule() {
  for (int i = 0; i < MAX_COROUTINE_NUMBER; ++i) {
    UContextPtr ptr(new CoroutineContext);
    ptr->id = i;
    ptr->stack.reset(new char[STACK_SIZE]);
    coroutines_.push_back(ptr);
  }
}
UContextPtr Schedule::GetCoroutineContext() {
  for (int i = 0; i < MAX_COROUTINE_NUMBER; ++i) {
    if (Instance()->coroutines_[i]->state == CoroutineState::CO_FINISHED) {
      return Instance()->coroutines_[i];
    }
  }
  return nullptr;
}
void Schedule::Yield() {
  LOG(INFO) << "yield id " << Instance()->runing_coroutine_id_;
  if (Instance()->runing_coroutine_id_ != -1) {
    auto ctx = Instance()->coroutines_[runing_coroutine_id_];
    ctx->state = CoroutineState::CO_SUSPEND;
    runing_coroutine_id_ = -1;
    swapcontext(&(ctx->ctx), &main_);
  }
}

int Schedule::CreateCoroutine(const std::string &name, CoFunc func, void *arg) {
  auto ctx = Instance()->GetCoroutineContext();
  if (ctx != nullptr) {
    ctx->func = func;
    ctx->arg = arg;
    ctx->name = name;
    ctx->force_stop = false;
    ctx->state = CoroutineState::CO_READY;
    getcontext(&(ctx->ctx));
    ctx->ctx.uc_stack.ss_sp = ctx->stack.get();
    ctx->ctx.uc_stack.ss_size = STACK_SIZE;
    ctx->ctx.uc_stack.ss_flags = 0;
    ctx->ctx.uc_link = &(Instance()->main_);
    Instance()->SetWorkId(ctx->id);
    makecontext(&(ctx->ctx), (void (*)(void))CoHelper, 0);
    swapcontext(&Instance()->main_, &(ctx->ctx));
    return ctx->id;
  } else {
    LOG(ERROR) << "ctx is nullptr";
    return -1;
  }
}
void Schedule::Resume(int id) {
  if (id < 0 && id >= coroutines_.size()) {
    return;
  }
  auto ctx = Instance()->coroutines_[id];
  if (ctx->force_stop) {
    ctx->state = CoroutineState::CO_FINISHED;
    return;
  }
  LOG(INFO) << "resume id " << id << std::endl;
  if (ctx->state == CoroutineState::CO_SUSPEND) {
    swapcontext(&(Instance()->main_), &(ctx->ctx));
  }
}

int Schedule::Finish() {
  if (Instance()->runing_coroutine_id_ != -1) {
    return 0;
  } else {
    for (int i = 0; i < Instance()->coroutines_.size(); ++i) {
      if (Instance()->coroutines_[i]->state != CoroutineState::CO_FINISHED) {
        return 0;
      }
    }
  }
  return 1;
}

} // namespace coroutine

void CoHelper(void) {
  auto ctx = coroutine::Schedule::Instance()->GetCurrentCoroutine();
  ;
  if (ctx != nullptr) {
    ctx->func(ctx->arg);
    ctx->state = coroutine::CoroutineState::CO_FINISHED;
    coroutine::Schedule::Instance()->SetWorkId(-1);
  }
}
